How to speed up your Next.js app with code splitting and smart loading strategies.
Milica MihajlijaX GitHub HomepageWhat will you learn?This post explains different types of codesplitting and how to usedynamic imports to speed up your Next.js apps.
Route-based and component-based code splittingBy default, Next.js splits your JavaScript into separate chunks for each route.When users load your application, Next.js only sends the code needed for theinitial route. When users navigate around the application, they fetch the chunksassociated with the other routes. Route-based code splitting minimizes theamount of script that needs to be parsed and compiled at once, which results infaster page load times.
While route-based code splitting is a good default, you can further optimize theloading process with code splitting on the component level. If you have largecomponents in your app, it's a great idea to split them into separate chunks.That way, any large components that are not critical or only render on certainuser interactions (like clicking a button) can be lazy-loaded.
Next.js supports dynamic import(),which allows you to import JavaScript modules (including React components)dynamically and load each import as a separate chunk. This gives youcomponent-level code splitting and enables you to control resource loading sothat users only download the code they need for the part of the site thatthey're viewing. In Next.js, these components are server-side rendered(SSR)by default.
Dynamic imports in actionThis post includes several versions of a sample app that consists of a simplepage with one button. When you click the button, you get to see a cute puppy. Asyou move through each version of the app, you'll see how dynamic imports aredifferent from staticimportsand how to work with them.
In the first version of the app, the puppy lives in components/Puppy.js. Todisplay the puppy on the page, the app imports the Puppy component inindex.js with a static import statement:
import Puppy from "../components/Puppy";To see how Next.js bundles the app, inspect the network trace in DevTools:
To preview the site, press View App. Then pressFullscreen.
Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
Click the Network tab.
Select the Disable cache checkbox.
Reload the page.
When you load the page, all the necessary code, including the Puppy.jscomponent, is bundled in index.js:
When you press the Click me button, only the request for the puppy JPEG isadded to the Network tab:
The downside of this approach is that even if users don't click the button tosee the puppy, they have to load the Puppy component because it's included inindex.js. In this little example that's not a big deal, but in real-worldapplications it's often a huge improvement to load large components only whennecessary.
Now check out a second version of the app, in which the static import isreplaced with a dynamic import. Next.js includes next/dynamic, which makes itpossible to use dynamic imports for any components in Next:
import Puppy from "../components/Puppy";import dynamic from "next/dynamic";// ...const Puppy = dynamic(import("../components/Puppy"));Follow the steps from the first example to inspect the network trace.
When you first load the app, only index.js is downloaded. This time it's0.5 KB smaller (it went down from 37.9 KB to 37.4 KB) because itdoesn't include the code for the Puppy component:
The Puppy component is now in a separate chunk, 1.js, which is loaded onlywhen you press the button:
Note: By default, Next.js names these dynamic chunks number.js, where number starts from 1.In real-world applications, components are often muchlarger, and lazy loading themcan trim your initial JavaScript payload by hundreds of kilobytes.
Dynamic imports with custom loading indicatorWhen you lazy-load resources, it's good practice to provide a loading indicatorin case there are any delays. In Next.js, you can do that by providing anadditional argument to the dynamic() function:
const Puppy = dynamic(() => import("../components/Puppy"), { loading: () => Loading...});To see the loading indictor in action, simulate slow network connection inDevTools:
To preview the site, press View App. Then pressFullscreen.
Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
Click the Network tab.
Select the Disable cache checkbox.
In the Throttling drop-down list, select Fast 3G.
Press the Click me button.
Now when you click the button it takes a while to load the component and the appdisplays the "Loading…" message in the meantime.
Dynamic imports without SSRIf you need to render a component only on the client side (for example, a chatwidget) you can do that by setting the ssr option to false:
const Puppy = dynamic(() => import("../components/Puppy"), { ssr: false,}); ConclusionWith support for dynamic imports, Next.js gives you component-level codesplitting, which can minimize your JavaScript payloads and improve applicationload time. All components are server-side rendered by default and you candisable this option whenever necessary.